package org.shoukaiseki.kakaroto;

import com.alibaba.fastjson2.JSONArray;
import org.shoukaiseki.common.exception.MessageVirtualException;
import org.shoukaiseki.common.utils.ListUtils;
import org.shoukaiseki.common.utils.MybatisUtils;
import org.shoukaiseki.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.shoukaiseki.common.utils.ListUtils.newArrayList;
import static org.shoukaiseki.common.utils.StringUtils.format;

/**
 * org.shoukaiseki.sks.FilterParser
 *  动态过滤解析器
 *  按照JPA 的sql标准
 *
 *  例如:  在查询的 主类中增加
 *  <pre>
 @JsonIgnore
 @JsonProperty
 @JSONField(serialize = false)
 public String getSksParamsWhereSql(){
 //        return new FilterParser(getSksParams(),"sksParams","gen_table").parser();
     return new FilterParser(getSksParams(),"gen_table").parser();
 //        return "and table_name=#{testLinux}";
 }
  *  </pre>
  *  mybatis xml 中添加
  *  <pre>
      <where>
         ${sksParamsWhereSql}
      </where>
  *  </pre>
  *
  *
  *  优先 customFilterParser 判断,  用于 java 拼接where
 *  如果返回null,则 判断 是否在 ignoreQueryParamName 中,  用于 xml 配置 where
 **/
public class FilterParser {

    public static final Logger logger= LoggerFactory.getLogger("org.shoukaiseki.sks");

    public static final String DEFAULT_QUERY_PARAMS_MYBATIS="sksParams";

    public static final String[] DEFAULT_IGNORE_QUERY_PARAM_NAME={"showVirtualBitFilter"};


//    /**
//     * 数据库对应的 java 类型字段
//     *
//     * 会按照 查询的属性类型 ,
//     */
//    private Class<?> qoClazz;

    //在mybatis中的参数名
    private String queryParamsMybatis=DEFAULT_QUERY_PARAMS_MYBATIS;

    private Map<String,Object> queryParams;

    //查询时使用的实际表和字段名(主要用于关联查询)
    //columnNameWhereName.put("itemNum","pro.itemNum");
    //如果设置属性,将不会再使用 tableAliasName 进行拼接
    private Map<String,String> columnNameWhereName=new HashMap<>();

    //where ${tableAliasName}.queryParams.key=${queryParams.key}
    private String tableAliasName;

    //表字段是否进行陀峰转换
    private boolean columnCamelCaseConvert=false;

    //忽略的查询参数名
    private List<String> ignoreQueryParamName=newArrayList(DEFAULT_IGNORE_QUERY_PARAM_NAME);

    //是否加反单引号
    private boolean backQuote;

    private CustomFilterParser customFilterParser=new CustomFilterParser() {
    };

    /**
     * @param queryParams           查询的动态参数
     * @param tableAliasName        where查询时字段的表名
     */
    public FilterParser(Map<String,Object> queryParams, String tableAliasName){
        this.queryParams=queryParams;
        this.tableAliasName=tableAliasName;
    }

    /**
     * @param queryParams           查询的动态参数
     * @param tableAliasName        where查询时字段的表名
     */
    public FilterParser(Map<String,Object> queryParams, String queryParamsMybatis, String tableAliasName){
        this.queryParams=queryParams;
        this.tableAliasName=tableAliasName;
        this.queryParamsMybatis=queryParamsMybatis;
    }


    //是否进行陀峰转换
    public FilterParser setColumnCamelCaseConvert(boolean columnCamelCaseConvert) {
        this.columnCamelCaseConvert = columnCamelCaseConvert;
        return this;
    }


    public FilterParser setIgnoreQueryParamName(String... ignoreQueryParamName) {
        return setIgnoreQueryParamName(newArrayList(ignoreQueryParamName));
    }

    public FilterParser setIgnoreQueryParamName(List<String> ignoreQueryParamName) {
        this.ignoreQueryParamName = ignoreQueryParamName;
        if(this.ignoreQueryParamName==null){
            this.ignoreQueryParamName=newArrayList(DEFAULT_IGNORE_QUERY_PARAM_NAME);
        }

        for (String tmp : DEFAULT_IGNORE_QUERY_PARAM_NAME) {
            if(!this.ignoreQueryParamName.contains(tmp)){
                this.ignoreQueryParamName.add(tmp);
            }
        }
        return this;
    }


    public FilterParser setCustomFilterParser(CustomFilterParser customFilterParser) {
        this.customFilterParser = customFilterParser;
        return this;
    }

    public FilterParser setBackQuote(boolean backQuote) {
        this.backQuote = backQuote;
        return this;
    }

    public FilterParser setColumnNameWhereName(Map<String, String> columnNameWhereName) {
        this.columnNameWhereName = columnNameWhereName;
        return this;
    }

    public String parser(){
        StringJoiner sj=new StringJoiner(" and "," and ","");
        sj.setEmptyValue(EMPTY);
        List<String> paramNameList = ListUtils.newArrayList(queryParams.keySet());
        for (String paramName : paramNameList) {
            Object value = queryParams.get(paramName);
            if (StringUtils.isEmpty(paramName)) {
                continue;
            }
            MybatisUtils.escapeTableOrColumnSql(paramName);
            String sql = customFilterParser.parserBeforeInterceptor(paramName,value, this);
            if (sql !=null) {
                if (StringUtils.isNotEmpty(sql)) {
                    sj.add(sql);
                }
                continue;
            }
            //忽略
            if (ignoreQueryParamName.contains(paramName)) {
                continue;
            }
            sql = mybatisParser(paramName, value);
            if (StringUtils.isNotEmpty(sql)) {
               sj.add(sql);
            }


        }
        String sql = sj.toString();
        logger.debug("{}",sql);
        return sql;
    }

    /**
     * 返回 null 或者空字符不会拼接
     * @param paramName
     * @param value
     * @return
     */
    public String mybatisParser(String paramName,Object value){
        String columnName="";
        String jpaName="";
        jpaName="Customize";
        if (paramName.endsWith(jpaName)) {
            //自定义的忽略
           return null;
        }

        if(value==null){
            return null;
        }
        if(value instanceof String&&StringUtils.isEmpty(value.toString())){
           return null;
        }

        jpaName="NotNull";
        if (paramName.endsWith(jpaName)) {
            //为1时启用过滤
            if(!StringUtils.equalsStringOne(value.toString())){
                return null;
            }
            if(paramName.endsWith("Is"+jpaName)){
                columnName = formatColumnName(paramName,"Is"+jpaName);
            }else{
                columnName = formatColumnName(paramName,jpaName);
            }
            return format(" {} not null ",columnName);
        }
        jpaName="Null";
        if (paramName.endsWith(jpaName)) {
            //为1时启用过滤
            if(!StringUtils.equalsStringOne(value.toString())){
                return null;
            }
            if (paramName.endsWith("Is"+jpaName)) {
                columnName = formatColumnName(paramName,"Is"+jpaName);
            }else{
                columnName = formatColumnName(paramName,jpaName);
            }
            return format(" {} is null ",columnName);
        }
        jpaName="NotEmpty";
        if (paramName.endsWith(jpaName)) {
            //为1时启用过滤
            if(!StringUtils.equalsStringOne(value.toString())){
                return null;
            }
            if (paramName.endsWith("Is"+jpaName)) {
                columnName = formatColumnName(paramName,"Is"+jpaName);
            }else{
                columnName = formatColumnName(paramName,jpaName);
            }
            return format(" {} !='' ",columnName);
        }
        jpaName="Empty";
        if (paramName.endsWith(jpaName)) {
            //为1时启用过滤
            if(!StringUtils.equalsStringOne(value.toString())){
                return null;
            }
            if (paramName.endsWith("Is"+jpaName)) {
                columnName = formatColumnName(paramName,"Is"+jpaName);
            }else{
                columnName = formatColumnName(paramName,jpaName);
            }
            return format(" {} ='' ",columnName);
        }


        jpaName="FindInSet";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" FIND_IN_SET({},{})",formatMybatisParam(paramName),columnName);
        }

        jpaName="NotLike";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {} not like concat('%',{},'%')",columnName,formatMybatisParam(paramName));
        }

        jpaName="Like";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {} like concat('%',{},'%')",columnName,formatMybatisParam(paramName));
        }

        jpaName="Equals";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {}={}",columnName,formatMybatisParam(paramName));
        }

        jpaName="Is";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {}={}",columnName,formatMybatisParam(paramName));
        }

        jpaName="LessThan";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {}<{}",columnName,formatMybatisParam(paramName));
        }


        jpaName="LessThanEqual";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {}<={}",columnName,formatMybatisParam(paramName));
        }

        jpaName="GreaterThan";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {}>{}",columnName,formatMybatisParam(paramName));
        }
        jpaName="GreaterZero";
        if (paramName.endsWith(jpaName)) {
            //为1时启用过滤
            if(!StringUtils.equalsStringOne(value.toString())){
                return null;
            }
            columnName = formatColumnName(paramName,jpaName);
            return format(" {}>0",columnName);
        }

        jpaName="GreaterThanEqual";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {}>={}",columnName,formatMybatisParam(paramName));
        }

        jpaName="Before";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {}<{}",columnName,formatMybatisParam(paramName));
        }

        jpaName="After";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {}>{}",columnName,formatMybatisParam(paramName));
        }


        jpaName="StartingWith";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {} like concat({},'%')",columnName,formatMybatisParam(paramName));
        }

        jpaName="EndingWith";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {} like concat('%',{})",columnName,formatMybatisParam(paramName));
        }

        jpaName="Containing";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {} like concat('%',{},'%')",columnName,formatMybatisParam(paramName));
        }

        jpaName="NotIn";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            return jsonToInSql(paramName,value,format(" {} not in (",columnName),")",true);
        }

        jpaName="In";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            return jsonToInSql(paramName,value,format(" {} in (",columnName),")");
        }

        jpaName="Not";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {} != {}",columnName,formatMybatisParam(paramName));
        }

        jpaName="AndTrue";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            String mybatisParam = formatMybatisParam(paramName);
            resetParamValueIsSingle(paramName);
            return format(" ({}&{})={}",columnName, mybatisParam, mybatisParam);
        }


        jpaName="AndFalse";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            String mybatisParam = formatMybatisParam(paramName);
            resetParamValueIsSingle(paramName);
            return format(" !({}&{})",columnName, mybatisParam, mybatisParam);
        }

        jpaName="True";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {} = True",columnName);
        }

        jpaName="False";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            resetParamValueIsSingle(paramName);
            return format(" {} = false",columnName);
        }

        jpaName="IgnoreCase";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
            String mybatisParam = formatMybatisParam(paramName);
            resetParamValueIsSingle(paramName);
            return format(" UPPER({}) = UPPER({})",columnName,mybatisParam);
        }

        jpaName="Between";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
//            String mybatisParam = formatMybatisParam(paramName);
            return jsonToBetweenSql(paramName,value,format(" {} ",columnName));
        }

        jpaName="StrRange";
        if (paramName.endsWith(jpaName)) {
            columnName = formatColumnName(paramName,jpaName);
//            String mybatisParam = formatMybatisParam(paramName);
            return jsonToStrRangeSql(paramName,value,format(" {} ",columnName));
        }


        return EMPTY;
        //其余的不进行处理,因为 0x01 之类的,不进行jpa处理,而且同一个QO对象会多次执行该方法
//        columnName = formatColumnName(paramName,EMPTY);
//        resetParamValueIsSingle(paramName);
//        return format(" {}={}",columnName,formatMybatisParam(paramName));
    }

    public String jsonToStrRangeSql(String paramName, Object json, String prefix){
        try{
            if(StringUtils.isEmpty(json.toString())){
                return EMPTY;
            }
            JSONArray jsonArray = JSONArray.parseArray(json.toString());
            if (jsonArray.size()!=2) {
                return null;
            }
            if (jsonArray.get(0)!=null&&jsonArray.get(1)!=null) {
                //新的参数名
                String beginParamName = StringUtils.format("{}0x01",paramName);
                String afterParamName = StringUtils.format("{}0x02",paramName);
                queryParams.put(beginParamName,jsonArray.get(0));
                queryParams.put(afterParamName,jsonArray.get(1));
                String sql = StringUtils.format("{} >= {} and {} <= {}",prefix,formatMybatisParam(beginParamName),prefix,formatMybatisParam(afterParamName));
                return sql;
            }
        }catch (Throwable t){
            logger.warn("{} 参数解析转 list 失败,参数不为 json 类型",paramName,json);
        }
        return null;
    }

    public String jsonToBetweenSql(String paramName, Object json, String prefix){
        try{
            if(StringUtils.isEmpty(json.toString())){
                return EMPTY;
            }
            JSONArray jsonArray = JSONArray.parseArray(json.toString());
            if (jsonArray.size()!=2) {
               return null;
            }
            if (jsonArray.get(0)!=null&&jsonArray.get(1)!=null) {
                //新的参数名
                String beginParamName = StringUtils.format("{}0x01",paramName);
                String afterParamName = StringUtils.format("{}0x02",paramName);
                queryParams.put(beginParamName,jsonArray.get(0));
                queryParams.put(afterParamName,jsonArray.get(1));
                String sql = StringUtils.format("{} between {} and {}",prefix,formatMybatisParam(beginParamName),formatMybatisParam(afterParamName));
                return sql;
            }
        }catch (Throwable t){
            logger.warn("{} 参数解析转 list 失败,参数不为 json 类型",paramName,json);
        }
        return null;
    }

    public String jsonToInSql(String paramName, Object paramValue, CharSequence prefix, CharSequence suffix){
        return jsonToInSql(paramName, paramValue, prefix, suffix,false);
    }

    /**
     *
     * @param paramName
     * @param json
     * @param prefix      x.id in (
     * @param suffix      )
     * @param noElementNofilter     true: 没有元素则不启用过滤
     * @return
     */
    public String jsonToInSql(String paramName, Object paramValue, CharSequence prefix, CharSequence suffix,boolean noElementNofilter){
        Object json=paramValue;
        try{
            if(json == null){
                return EMPTY;
            }
            List jsonArray = null;
            if(json instanceof Stream){
                json=((Stream)json).collect(Collectors.toList());
            }
            if(json instanceof Iterable){
                jsonArray= ListUtils.newArrayList((Iterable) json);
            }else{
                if(StringUtils.isEmpty(json.toString())){
                    return EMPTY;
                }
                jsonArray = JSONArray.parseArray(json.toString());
            }
            StringJoiner sj=new StringJoiner(",",prefix,suffix);
            sj.setEmptyValue(EMPTY);
            int i=0;
            if(jsonArray.isEmpty()){
                if(noElementNofilter){
                    return EMPTY;
                }
                //无记录
                return "1=2";
            }
            for (Object val : jsonArray) {
                i++;
                //新的参数名
                String newParamName = StringUtils.format("{}0x{}",paramName,String.format("%02x", i));
                queryParams.put(newParamName,val);
                sj.add(formatMybatisParam(newParamName));
            }
            return sj.toString();
        }catch (Throwable t){
            logger.warn("{} 参数解析转 list 失败,参数不为 json 类型",paramName,json);
        }
            return null;
    }

    //包含
    public String formatColumnName(String queryName,String jpaName){
        String columnName=queryName.substring(0,queryName.lastIndexOf(jpaName));
        //如果自定义查询某个字段,则使用自定义的信息
        if(columnNameWhereName.containsKey(columnName)){
            return columnNameWhereName.get(columnName);
        }
        if (this.columnCamelCaseConvert) {
            columnName= StringUtils.camelToLine(columnName);
        }
        if(backQuote){
            if(StringUtils.isEmpty(tableAliasName)){
                return format("`{}`",columnName);
            }
            return format("`{}`.`{}`",tableAliasName,columnName);
        }
        if(StringUtils.isEmpty(tableAliasName)){
            return columnName;
        }
        return format("{}.{}",tableAliasName,columnName);
    }


    public String formatMybatisParam(String paramName){
        if(StringUtils.isEmpty(this.queryParamsMybatis)){
            return "#{"+paramName+"}";
        }
        return "#{"+this.queryParamsMybatis+"."+paramName+"}";
    }

    //重置 参数值 为单一的,如果值时list,set 类型,将会获取第一个元素,如果存在多个则 抛错
    public void resetParamValueIsSingle(String paramName){
        Object o = queryParams.get(paramName);
        if(o == null){
           return ;
        }
        if (o instanceof Iterable) {
            List list = newArrayList((Iterable) o);
            if (list.size()>1) {
                throw new MessageVirtualException("{} 存在多个值,过滤条件只支持一个参数值").format(paramName);
            }
            if (list.size()==0) {
                queryParams.put(paramName,null);
                return;
            }
            queryParams.put(paramName,list.get(0));
        }
    }
}
